/*
 * TextConfigFile.java
 *
 * Created on 20 de Abril de 2011, 16:10
 *
 */

package intopo.evogame.partnerselection.config;

import cern.jet.random.engine.MersenneTwister;
import intopo.evogame.partnerselection.Cache;
import intopo.game.DescriptionText;
import intopo.game.Game;
import intopo.game.Strategy;
import java.io.FileNotFoundException;
import java.lang.reflect.InvocationTargetException;
import java.util.Scanner;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Read a configuration file in order to construct a game and a population of player, to initialise global parameters and to cache constant values.
 *
 * Reads number from the scanner in order to initialise attributes of class {@code Parameters}.  Each number should be in a distinct line.  The line must start with the number. Any character following the number is discarded. The format of the data is:
 * <pre>
 * number of rounds
 * mutation probability
 * standard deviation of the normal distribution used to mutate pool size gene
 * standard deviation of the normal distribution used to mutate delta gene
 * energy required to reproduce
 * maximum value of pool size gene
 * carrying capacity
 * </pre>

 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Lopes da Silva Mariano</a>
 *
 * @version 1.0 20 de Abril de 2011
 */
abstract public class AbstractTextConfigFile<
		D extends DescriptionText<G, S>,
		G extends Game<S>,
		S extends Strategy<S>>
	extends AbstractConfigFile<D, G, S>
{
	static private Pattern wordPattern = Pattern.compile ("\\S+");
	/**
	 * 
	 * @param description
	 */
	protected AbstractTextConfigFile (D description)
	{
		super (description);
	}
	/**
	 * Read the given configuration file.
	 *
	 * @param filename
	 * @throws java.io.FileNotFoundException
	 * @throws intopo.evogame.partnerselection.TagException
	 * @throws intopo.evogame.partnerselection.config.ConstructGameException
	 */
	public void read (String filename)
		throws
			FileNotFoundException,
			TagException,
			ConstructGameException,
			ConstructStrategyException,
			IOException,
			ParsingException
	{
		Scanner scanner;
		scanner = new Scanner (new FileInputStream (filename));
		scanner.useLocale (java.util.Locale.ROOT);
		this.readGlobalParameters (scanner);
		this.description.readGlobalParameters (scanner);
		this.readGame (scanner);
		this.readPopulation (scanner);
		scanner.close ();
	}
	/**
	 *
	 * @param filename
	 * @throws java.io.FileNotFoundException
	 */
	public void write (String filename)
		throws FileNotFoundException
	{
		PrintStream ps = new PrintStream (filename);
		this.writeGlobalParameters (ps);
		ps.println ();
		this.description.writeGlobalParameters (ps);
		ps.println ();
		this.writeGame (ps);
		ps.println ();
		this.writePopulation (ps);
		ps.close ();
	}
	/**
	 * Reads global parameters from the scanner that encapsulates a text file.
	 * @param scanner
	 */
	abstract protected void readGlobalParameters (Scanner scanner)
		throws ParsingException;
	/**
	 * 
	 * @param ps
	 */
	abstract protected void writeGlobalParameters (PrintStream ps);
	/**
	 * Read a population of players.  This depends on
	 * @param scanner
	 */
	abstract protected void readPopulation (Scanner scanner)
		throws
			ConstructStrategyException,
			TagException,
			ParsingException,
			IOException;

	abstract protected void writePopulation (PrintStream ps);
	/**
	 * Read data from the given scanner and construct a new game instance.
	 *
	 * <p><b>Text File</b> the format of the text file is a word followed by any game parameters that the constructor consumes.  The word is used to obtain
	 * a constructor stored in the game description.
	 *
	 * @param scanner The scanner where the game data is read.  This scanner encapsulates the text file opened in method {@code read(String)}.
	 *
	 * @throws intopo.evogame.partnerselection.TagException This exception is thrown when the word tag does not correspond to any key in
	 * the associative memory in the game description.
	 *
	 * @throws intopo.evogame.partnerselection.config.ConstructGameException This exception is thrown when the game constructor fails.  It encapsulates all the exceptions that can be
	 * thrown by method {@code Constructor"newInstance}.
	 *
	 * @see java.lang.reflect.Constructor#newInstance(java.lang.Object[])
	 *
	 * @see #read(java.lang.String)
	 */
	final protected void readGame (Scanner scanner)
		throws TagException, ConstructGameException
	{
		Map<String, Constructor<? extends G>> map = this.description.getGameConstructor ();
		String word = scanner.next (wordPattern);
		Constructor<? extends G> c;
		if (map.containsKey (word)) {
			c = map.get (word);
			try {
				this.game = c.newInstance (scanner);
			}
			catch (InstantiationException ex) {
				throw new ConstructGameException (word, ex);
			}
			catch (IllegalAccessException ex) {
				throw new ConstructGameException (word, ex);
			}
			catch (IllegalArgumentException ex) {
				throw new ConstructGameException (word, ex);
			}
			catch (InvocationTargetException ex) {
				throw new ConstructGameException (word, ex);
			}
		}
		else {
			throw new TagException (TagException.Entity.GAME, word);
		}
	}

	final protected void writeGame (PrintStream ps)
	{
		ps.println ();
		this.game.write (ps);
		ps.println ();
		ps.println ();
	}
	/**
	 * Read a strategy from the given scanner that encapsulates the text configuration file.
	 * 
	 * @param scanner
	 * @return
	 * @throws intopo.evogame.partnerselection.config.ConstructStrategyException
	 * @throws intopo.evogame.partnerselection.config.TagException
	 */
	final protected S readStrategy (Scanner scanner)
		throws ConstructStrategyException, TagException
	{
		Map<String, Constructor<? extends S>> map = this.description.getStrategyConstructors ();
		String word = scanner.next (wordPattern);
		Constructor<? extends S> c;
		if (map.containsKey (word)) {
			c = map.get (word);
			try {
				S result = c.newInstance (scanner);
				return result;
			}
			catch (InstantiationException ex) {
				throw new ConstructStrategyException (word, ex);
			}
			catch (IllegalAccessException ex) {
				throw new ConstructStrategyException (word, ex);
			}
			catch (IllegalArgumentException ex) {
				throw new ConstructStrategyException (word, ex);
			}
			catch (InvocationTargetException ex) {
				throw new ConstructStrategyException (word, ex);
			}
		}
		else {
			throw new TagException (TagException.Entity.STRATEGY, word);
		}
	}
	/**
	 * Parse the next float in the given scanner.  After reading the float,
	 * the rest of the line is read.  If the scanner is empty or the next
	 * token is not a float an exception is throw.
	 */
	static public float readFloat (Scanner scanner, String errorMsg)
		throws ParsingException
	{
		if (scanner.hasNextFloat ()) {
			float result = scanner.nextFloat ();
			scanner.nextLine ();
			return result;
		}
		else {
			if (scanner.hasNext ()) {
				throw new ParsingException ("Expecting a float value for " + errorMsg, scanner.nextLine ());
			}
			else {
				throw new ParsingException ("Expecting a float value for " + errorMsg);
			}
		}
	}
	/**
	 * Parse the next integer in the given scanner.  After reading the integer,
	 * the rest of the line is read.  If the scanner is empty or the next
	 * token is not a float an exception is throw.
	 */
	static public int readInt (Scanner scanner, String errorMsg)
		throws ParsingException
	{
		if (scanner.hasNextFloat ()) {
			int result = scanner.nextInt ();
			scanner.nextLine ();
			return result;
		}
		else if (scanner.hasNext ()) {
			throw new ParsingException ("Expecting an int value for " + errorMsg, scanner.nextLine ());
		}
		else {
			throw new ParsingException ("Expecting an int value for " + errorMsg);
		}
	}
	/**
	 * Parse the next word in the given scanner.  After reading the word,
	 * the rest of the line is read.  If the scanner is empty or the next pattern does not match a word, an exception is thrown.
	 * 
	 * @param scanner
	 * @param errorMsg
	 * @return
	 * @throws intopo.evogame.partnerselection.config.ParsingException
	 */
	static public String readWord (Scanner scanner, String errorMsg) throws ParsingException
	{
		if (scanner.hasNext (wordPattern)) {
			String result = scanner.next (wordPattern);
			scanner.nextLine ();
			return result;
		}
		else if (scanner.hasNext ()) {
			throw new ParsingException ("Expecting a word value for " + errorMsg, scanner.nextLine ());
		}
		else {
			throw new ParsingException ("Expecting a word value for " + errorMsg);
		}
	}

}
